use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
use std::fmt;
use std::convert::TryFrom;

pub fn random_float() -> Result<f32, Box<dyn Error>> {
    let mut f = File::open("/dev/urandom")?;
    let mut buffer = [0; 4];

    // read up to 4 bytes
    f.read(&mut buffer)?;
    
    
    Ok(f32::from_ne_bytes(buffer))
}

// returns a f32 value between zero and one
pub fn uniform_variate() -> Result<f32, Box<dyn Error>> {
    let mut f = File::open("/dev/urandom")?;
    let mut buffer = [0; 2];

    // read up to 4 bytes
    f.read(&mut buffer)?;
    
    let summand = u16::from_le_bytes(buffer);
    
    Ok(f32::from(summand) / f32::from(u16::MAX))
}

#[derive(Debug)]

struct NoElementsError {}

impl Error for NoElementsError {}

impl fmt::Display for NoElementsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {     
        write!(f, "Sakra di!")
    }
}

pub fn choose_uniformly_from<T: Copy>(items: &[T]) -> Result<T, Box<dyn Error>> {
    if items.len() == 0 {
        Err(Box::new(NoElementsError {}))
    } else {
        let sp_per_item: f32 = 1f32 / f32::from(u16::try_from(items.len()).unwrap());
        let rndsample: f32 = uniform_variate()?;
        let mut cnt: usize = 0;
        let mut upper_threshold: f32 = sp_per_item;

        let mut idx = 0;
        for _ in items.iter() {
            if rndsample <= upper_threshold {
                idx = cnt;
                break;
            } else {
                cnt = cnt + 1;
                upper_threshold = upper_threshold + sp_per_item;
            }
        }
        Ok(items[idx])
    }

    
}

#[derive(Clone)]
#[derive(Debug)]
pub struct RandomItem<T: Copy> {
    pub item: T,
    pub prob: f32,
}

#[derive(Debug)]
pub struct BiasedChooser<T: Copy> {
    items: Vec<RandomItem<T>>
}

#[derive(Debug)]
pub struct InvalidProbabilitiesError {}

impl Error for InvalidProbabilitiesError {}

impl fmt::Display for InvalidProbabilitiesError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {     
        write!(f, "Sakra di!")
    }    
}

#[derive(Debug)]
pub struct NoItemError {}

impl Error for NoItemError {}

impl fmt::Display for NoItemError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {     
        write!(f, "Sakra di!")
    }    
}



impl<T: Copy> BiasedChooser<T> {
    pub fn new(items: Vec<RandomItem<T>>) -> Result<BiasedChooser<T>, InvalidProbabilitiesError> {
        let mut active_items = Vec::new();
        let mut sum: f32 = 0f32;
        for it in items.iter() {
            if it.prob > 0f32 {
                active_items.push(it.clone());
                sum = sum + it.prob;
            }
        }
        
        if (sum - 1f32).abs() > f32::EPSILON  {
            Err(InvalidProbabilitiesError {})
        } else {
            Ok(BiasedChooser { items: active_items })
        }
    }

    pub fn choose(&self) -> Result<T, Box<dyn Error>> {
        let sample_result = uniform_variate();
        let sample = match sample_result {
            Ok(sam) => sam,
            Err(e) => return Err(e),
        };

        let mut chosen_item: Option<T> = None;
        let mut upper_threshold = 0f32;
        for n in self.items.iter() {
            upper_threshold = upper_threshold + n.prob;
            if sample <= upper_threshold {
                chosen_item = Some(n.item);
                break;
            }
        }

        match chosen_item {
            Some(it) => Ok(it),
            None => Err(Box::new(NoItemError {}))
        }
        
    }
}
